package intopo.game.impl.pgp;

import intopo.evogame.partnerselection.config.ParsingException;
import intopo.game.Strategy;
import intopo.util.Util;
import intopo.evogame.partnerselection.Cache;

import util.xml.*;

import cern.jet.random.engine.RandomEngine;

import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import java.io.IOException;
import java.io.PrintStream;
import org.w3c.dom.Element;


/**
 * Base class of all classes that represent a player of the Public Good
 * Provision game. Since there are only two actions in this game (provide
 * the good or do nothing), this class defines a method returns {@code true}
 * if the player provides the good.

 * <p> This class represents a probabilistic player since it has a field
 * that represents the probability of the player providing the good or not.

 * <p> This class uses the pseudo-random number generator stored in field {@code intopo.evogame.partnerselection.Cache} to calculate if this strategy provides the good or not.
 *
 * @see PublicGoodProvisionGame
 * @see intopo.evogame.partnerselection.Cache

 * @author <a href="mailto:plsm@det.ua.pt">Pedro Lopes da Silva Mariano</a>

 * @version 2.0
 */
final public class PublicGoodProvisionStrategy
	implements Strategy<PublicGoodProvisionStrategy>
{
	/**
	 * 
	 */
	static String CONSTRUCTOR_TAG = "PublicGoodProvisionStrategy";
	/**
	 * The probability of this player to provide the good.
	 */
	final private float provideProbability;
	/**
	 * Construct a new strategy with the given probability to provide the good.
	 * <p><b>Pre-condition</b> provide probability must be in the closed interval [0.0f;1.0f].
	 * @param provideProbability The probability to provide the good.
	 */
	public PublicGoodProvisionStrategy (float provideProbability)
	{
		assert 0 <= provideProbability && provideProbability <= 1;

		this.provideProbability = provideProbability;
	}
	/**
	 * Construct a new strategy with the data stored in the given XML element.
	 * @param element
	 * @throws util.xml.XMLParseException
	 */
	public PublicGoodProvisionStrategy (Element element)
		throws XMLParseException
	{
		AttributeParser<Double> apProb = new AttributeParser<Double>
			(new CheckExistenceAttribute<Double> (),
			 new ParseAsRandomDouble (),
			 new ParseAsDouble (0, 1));

		provideProbability = apProb.parse (element, "ProvideProbability", "value").floatValue ();
		assert 0 <= provideProbability && provideProbability <= 1;
	}
	/**
	 * Construct a new strategy based on the data available in the given scanner.
	 * @param scanner
	 */
	public PublicGoodProvisionStrategy (java.util.Scanner scanner)
	{
		this (scanner.nextFloat ());
	}
	/**
	 * Return a copy of this strategy.
	 * @return a copy of this strategy.
	 */
	public PublicGoodProvisionStrategy clone ()
	{
		return new PublicGoodProvisionStrategy (provideProbability);
	}
	/**
	 * Get the probability to provide the good.
	 * @return the probability to provide the good.
	 */
	public float getProvideProbability ()
	{
		return provideProbability;
	}

	/**
	 * Return {@code true} if the player provides the good. This depends on
	 * the probability of providing the good.
	 */
	public boolean provides ()
	{
		return Cache.supply.nextFloat () < provideProbability;
	}

	public int getBin ()
	{
		int r = (int) Math.floor (provideProbability * Parameters.probability);
		if (r == Parameters.probability) {
			return r - 1;
		}
		else {
			return r;
		}
	}
	static String getBinDescription (int bin)
	{
		float p;
		p = bin / (float) Parameters.probability;
		return Float.toString (p);
	}

	public boolean isCooperator ()
	{
		return this.provideProbability >= 0.9;
	}
	
	public PublicGoodProvisionStrategy getMutantAllGenes ()
	{
		float p = Util.perturbNormal (this.provideProbability, Parameters.stddevNormalProbability, 0f, 1f);
		return new PublicGoodProvisionStrategy (p);
	}

	public PublicGoodProvisionStrategy getMutantSingleGene ()
	{
		float p = Util.perturbNormal (this.provideProbability, Parameters.stddevNormalProbability, 0f, 1f);
		return new PublicGoodProvisionStrategy (p);
	}

	public void print (PrintStream ps)
	{
		ps.print (PublicGoodProvisionStrategy.CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (provideProbability);
	}
	/**
	 * Writes this strategy to the given binary stream.
	 * <p> The data format is:
	 * <pre>
	 * | byte 1 | byte 2 | byte 3 | byte 4 |
	 * |        provide probability        |
	 * |                                   |
	 * </pre>
	 * @param dos The binary stream where the strategy is written.
	 * @throws java.io.IOException If an I/O error occurs.
	 */
	@Override
	public void write (java.io.DataOutputStream dos)
		throws IOException
	{
		dos.writeFloat (this.provideProbability);
	}

	@Override
	public boolean equals (Object other)
	{
		try {
			PublicGoodProvisionStrategy player = (PublicGoodProvisionStrategy) other;
			return this.provideProbability == player.provideProbability;
		}
		catch (java.lang.ClassCastException e) {
			throw new Error (e);
		}
	}
	@Override
	public int hashCode ()
	{
		return Float.floatToIntBits (this.provideProbability);
	}
	@Override
	public String toString ()
	{
		return String.valueOf (provideProbability);
	}
	/**
	 * Provides static fields used in calculating the <i>bin</i> of the enclosing strategy.
	 */
	static public class Parameters
	{
		/**
		 * Number of bins...
		 */
		static public int probability = 3;
		/**
		 * Standard deviation of the normal distribution used to mutate the probability to provide the good of a strategy.
		 */
		static public float stddevNormalProbability;
		/**
		 * Reads parameters that control the behaviour of instances of class {@code PublicGoodProvision}.
		 */
		static public void read (java.util.Scanner scanner) throws ParsingException
		{
			probability = AbstractTextConfigFile.readInt (scanner, "number of bins (probability)");
			stddevNormalProbability = AbstractTextConfigFile.readFloat (scanner, "standard deviation normal distribution mutate provision probability");
		}

		static void write (PrintStream ps)
		{
			ps.print (probability);
			ps.println ("\t# number of bins (probability)");
			ps.print (stddevNormalProbability);
			ps.println ("\t# standard deviation normal distribution mutate provision probability");
		}
	}
}

